Pull/Run the docker image containing molecule and friends, Mount the playbooks and roles repository, Run the desired test suite (handled by ansible/tests/detox_wrapper.sh)
Mons, 2019-03-21
Fabrice Flore-Thebault
User & contributor in Molecule / Ansible ecosystem.
Free Software Infrastructure Automation.
Culture, Automation, Measurement, Sharing.
Day 2 routines: system patches, audit, inventory.
Reproductible provisioning, from hypervisor to apps.
Automated backup & restore data.
Maintain environments on shared hosting platforms.
Deploy software.
Build CI pipelines.
Manage everything API: network, cloud, kubernetes.
Validate roles and playbooks before production
Instantiate temporary infrastructure
| Best practices to get further: https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html |
Molecule has many friends in the toolbox.
Ansible ecosystem
Platforms backends
Dependency backends
Verifiers
Ansible is an IT automation tool. It can configure systems, deploy software, and orchestrate more advanced IT tasks such as continuous deployments or zero downtime rolling updates. Ansible’s main goals are simplicity and ease-of-use.
Requirements: Python 2.7 or >= 3.6
sudo apt-get install -y python-pip libssl-dev
pip install --user ansibleMolecule is designed to aid in the development and testing of Ansible roles. […] Molecule is opinionated in order to encourage an approach that results in consistently developed roles that are well-written, easily understood and maintained.
Requirements: Ansible
sudo apt-get install -y python-pip libssl-dev
pip install --user moleculeImprove the roles quality.
Kill opinion wars.
Installed as a molecule dependency.
Ansible Lint is a commandline tool for linting playbooks. Use it to detect behaviors and practices that could potentially be improved.
(local) Virtualization
Cloud provider
Bake your own
Docker
LXC
LXD
Vagrant
Azure
EC2
GCE
Linode
Openstack
| Slow! Keep it for specific cloud features, Windows. |
Delegated
Audit the state of the tested platform after role execution with an independant tool.
Testinfra
Goss
Inspec
Default verifier.
Write tests in python.
Public == developers.
Easy. YAML syntax, fit well in the Ansible ecosystem.
Fast. Near instantaneous.
Small. <10MB single self-contained binary.
Linux only.
Goss is a YAML based serverspec alternative tool for validating a server’s configuration.
Complex, ruby based, with a feature full DSL.
Linux, MacOS and Windows support.
InSpec is compliance as code. Turn your compliance, security, and other policy requirements into automated tests.
$ molecule matrix -s default test
--> Test matrix
└── default
├── lint
├── destroy
├── dependency
├── syntax
├── create
├── prepare
├── converge
├── idempotence
├── side_effect
├── verify
└── destroyEnforce syntax rules (Ansible-lint, Yamllint).
Kill opinion wars and improve the roles quality.
--> Executing Ansible Lint on molecule/default/playbook.yml...
[701] No 'galaxy_info' found
meta/main.yml:1
[306] Shells that use pipes should set the pipefail option
molecule/default/playbook.yml:20
Task/Handler: shell | get version of common_linux
[206] Variables should have spaces before and after: {{ var_name }}
tasks/task_60_cron.yml:19
path: "/etc/cron.{{cron_item}}"Destroy the temporary platforms.
molecule destroy
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) deletion to complete] *******************************
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0Ensure the external requirements are installed
Supported backends:
galaxy
gilt
shell
--> Action: 'dependency'
- downloading role 'repo-remi', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-repo-remi/archive/1.2.0.tar.gz
- extracting geerlingguy.repo-remi to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.repo-remi
- geerlingguy.repo-remi (1.2.0) was installed successfully
- downloading role 'apache', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-apache/archive/3.0.3.tar.gz
- extracting geerlingguy.apache to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.apache
- geerlingguy.apache (3.0.3) was installed successfully
- downloading role 'mysql', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-mysql/archive/2.9.4.tar.gz
- extracting geerlingguy.mysql to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.mysql
- geerlingguy.mysql (2.9.4) was installed successfully
- downloading role 'php-versions', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php-versions/archive/3.0.0.tar.gz
- extracting geerlingguy.php-versions to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.php-versions
- geerlingguy.php-versions (3.0.0) was installed successfully
- downloading role 'php', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php/archive/3.7.0.tar.gz
- extracting geerlingguy.php to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.php
- geerlingguy.php (3.7.0) was installed successfully
- downloading role 'php-mysql', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php-mysql/archive/2.0.2.tar.gz
- extracting geerlingguy.php-mysql to /tmp/molecule/ansible-role-phpmyadmin/default/roles/geerlingguy.php-mysql
- geerlingguy.php-mysql (2.0.2) was installed successfullyComplementary to lint.
Perform a syntax check on the playbook.
ansible-playbook --syntax-check
molecule syntax
--> Validating schema molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix
└── default
└── syntax
--> Scenario: 'default'
--> Action: 'syntax'
playbook: molecule/default/playbook.ymlCreate the temporary platforms needed to execute the tests.
Faster and more reliable results are achieved with local drivers.
Remote drivers because are slow and error prone.
--> Scenario: 'default'
--> Action: 'create'
PLAY [Create] ******************************************************************
TASK [Log into a Docker registry] **********************************************
skipping: [localhost] => (item=None)
TASK [Create Dockerfiles from image names] *************************************
skipping: [localhost] => (item=None)
TASK [Discover local Docker images] ********************************************
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Build an Ansible compatible image] ***************************************
skipping: [localhost] => (item=None)
TASK [Create docker network(s)] ************************************************
TASK [Create molecule instance(s)] *********************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) creation to complete] *******************************
FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (299 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (298 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (297 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (296 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (295 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (294 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (293 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (292 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (291 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (290 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (289 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (288 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (287 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (286 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (285 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (284 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (283 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (282 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (281 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (280 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (279 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (278 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (277 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (276 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (275 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (274 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (273 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (272 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (271 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (270 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (269 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (268 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (267 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (266 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (265 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (264 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (263 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (262 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (261 retries left).
FAILED - RETRYING: Wait for instance(s) creation to complete (260 retries left).
changed: [localhost] => (item=None)
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0Optional.
Executes actions which bring the system to a given state prior to converge.
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.Execute the main role.
--> Scenario: 'default'
--> Action: 'converge'
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [instance]
TASK [Update apt cache.] *******************************************************
skipping: [instance]
TASK [geerlingguy.repo-remi : Install remi repo.] ******************************
changed: [instance]
TASK [geerlingguy.repo-remi : Import remi GPG key.] ****************************
changed: [instance]
TASK [geerlingguy.apache : Include OS-specific variables.] *********************
ok: [instance]
TASK [geerlingguy.apache : Include variables for Amazon Linux.] ****************
skipping: [instance]
TASK [geerlingguy.apache : Define apache_packages.] ****************************
ok: [instance]
[...]The idempotence sequence is key
The main role is executed again; the result should change nothing in order to achieve idempotence.
Idempotence is a goal difficult to achieve, particularly on Windows.
Optional
Executes actions which are not in the role, after converge.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side_effect playbook not configured.Executing an audit tool to verify that the final state is meeting expectations.
Supported backends:
testinfra - default, python based.
Goss - Linux only. Easy and fast.
Inspec - ruby DSL, works well for Windows targets.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/fab/src/themr0c/talk-jdl2019/examples/geerlingguy.phpmyadmin/molecule/default/tests/...
============================= test session starts ==============================
platform linux2 -- Python 2.7.16, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/fab/src/themr0c/talk-jdl2019/examples/geerlingguy.phpmyadmin/molecule/default, inifile:
plugins: testinfra-1.16.0
collected 1 item
tests/test_default.py . [100%]
========================== 1 passed in 12.76 seconds ===========================
Verifier completed successfully.Destroy the temporary platforms.
molecule destroy
--> Action: 'destroy'
PLAY [Destroy] *****************************************************************
TASK [Destroy molecule instance(s)] ********************************************
changed: [localhost] => (item=None)
changed: [localhost]
TASK [Wait for instance(s) deletion to complete] *******************************
ok: [localhost] => (item=None)
ok: [localhost]
TASK [Delete docker network(s)] ************************************************
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0Objective: validate that all roles are in a good shape, ready to deliver.
Test one role with molecule
Test multiple roles with tox
Automate on Continuous Integration platform
Before committing any changes to Git (tradeoffs: blocking, slow, antivirus).
Execute all tests on the named role ${rolename}:
cd ${rolename}
molecule test --allOrchestrate tests on a collection of roles.
Isolated python virtual environments.
tox aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software.
pip install tox virtualenvtox.iniMolecule role == Tox named environment
Running platforms are not isolated!
[tox]
envlist =
my_example_role
another_role
skipsdist = true
[testenv]
basepython = python3
commands = bash -c "(cd {toxinidir}/roles/{envname} && molecule --debug
test --all)"
description = molecule test role {envname}
deps = -r {toxinidir}/requirements.txt
setenv = MOLECULE_EPHEMERAL_DIRECTORY={envname}
sitepackages = true
whitelist_externals =
/bin/bash
/usr/bin/rubocoptox (1)
tox -e ${rolename} (2)
| 1 | Execute all molecule tests on all roles |
| 2 | Same, limited to the named role ${rolename} |
On pull request. Never merge broken code!
At commit on release branches / tags.
Foresee long compute time !
Need to run privileged Docker containers
We run molecule from a docker container, and from this docker container we need to create other container: we need access to the docker socket!
Current implementation of docker support in Bamboo doesn’t allow this (unsafe) behaviour.
We had to bake our own docker orchestration sequence (handled by ansible/tests/tests_suite.sh)
Pull/Run the docker image containing molecule and friends, Mount the playbooks and roles repository, Run the desired test suite (handled by ansible/tests/detox_wrapper.sh)
We have one unique Git repository containing the complete collection of all roles. It means that when CI runs the tests, all roles should be tested. We would avoid a lot of complication if each role would have its own repository. It is a bad architectural choice, but a business decision and we have to live with it.
Tox is able to run the tests in parallel, instead of sequentially. But blind parallelization on one unique agent is a bad idea our usecase.
To test the elasticsearch and redis roles for instance, we create complex topologies which use a lot of memory. As a consequence we need to fine tune the orchestration to avoid out of memory errors when running to many memory hungry tests in parallel.
Given on CI platform (bamboo), the best approach we found was to group roles in multiple test suites, each suite being run on a different agent (handled by ansible/tests/detox_wrapper.sh).
all elasticsearch related roles, to test sequentially
all redis related roles, to test sequentially
all other linux related roles, candidates to maximize parallelism
all azure/windows bound roles, also candidates to maximize parallelism (and subject to high error rate)
Kubernetes Operator SDK
geerlingguy.phpmyadmin
Create our own
git
docker version 17.03+.
kubectl version v1.9.0+.
ansible version v2.6.0+
ansible-runner version v1.1.0+
ansible-runner-http version v1.0.0+
dep version v0.5.0+.
go version v1.10+.
Access to a Kubernetes v.1.9.0+ cluster.
export GOPATH=$HOME/.go
mkdir -p $GOPATH/src/github.com/operator-framework
cd $GOPATH/src/github.com/operator-framework
git clone https://github.com/operator-framework/operator-sdk
cd operator-sdk
git checkout master
make dep
make installoperator-sdk new memcached-operator --api-version=cache.example.com/v1alpha1 --kind=Memcached --type=ansible
cd memcached-operatormkvirtualenv molecule
pip install molecule docker
git clone git@github.com:geerlingguy/ansible-role-phpmyadmin.git geerlingguy.phpmyadmin
cd ansible-role-phpmyadmin.git
molecule testDemo time
ansible-lint and molecule are great tools. They’ve been built and tested by the community that we see as essential parts of enhancing development of Ansible automation. By adopting these tools, Red Hat intends to invest resources working with the community to make them even better.
We want molecule to become the defacto standard tool, or sdk if you will, for content creators.